package mammuthus.client

import java.util
import java.util.Date
import java.util.concurrent.ConcurrentHashMap
import com.google.inject.Inject
import com.webdm.common.tools.TimeUtil
import mammuthus.client.ConfUtils
import net.csdn.common.settings.Settings
import org.I0Itec.zkclient.IZkChildListener
import org.slf4j.LoggerFactory

import scala.beans.BeanProperty
import scala.collection.JavaConversions._

/**
 * Created by fbj on 15-2-4 上午10:58.
 */
class ConfClient @Inject()(settings: Settings, @BeanProperty var zKClientWrapper: ZKClientWrapper){

  @BeanProperty var confRoot = settings.get("conf.root")
 // @BeanProperty var confNames = settings.get("conf.names")

  val logger = LoggerFactory.getLogger(classOf[ConfClient])

  logger.info(s"""初始化配置文件客户端,confRoot ${confRoot}""")

  //配置文件回调列表
  val confListenerMap = new ConcurrentHashMap[String, ConfCallBack](100)

  //依赖对应表 (path, 谁依赖他)
  val confDependencyMap = new ConcurrentHashMap[String, util.Set[String]](100)

  //配置文件和他的依赖对应关系 (path, 都依赖谁)
  val confMap = new ConcurrentHashMap[String, util.Set[String]](100)

  val pathChildDetail = new ConcurrentHashMap[String, util.Set[String]](100)

  //处理所有文件变动的事件,因为有依赖,所以需要包装一层事件
  val confCallBack = new ConfCallBack {

    val key = System.currentTimeMillis().toInt

    override def confChange(path: String, conf: String): Unit = {

      logger.info("配置文件发生变化{}, len {}", path, if(conf == null) 0 else conf.length)

      processFile(path)

      //处理依赖他的所有配置
      if(confDependencyMap.containsKey(path)){

        confDependencyMap.get(path).foreach(p => {

          if(confListenerMap.containsKey(p)){
            logger.info("处理依赖变动{}", p)
            val result = processConfChange(getConf(getPath(p)), confMap.get(p))
            confListenerMap.get(p).confChange(p.replace(confRoot, ""), result)
          }
        })

      }

//      logger.info("[" + path+"]")
//      logger.info(confListenerMap.get(path) + "")
//      confListenerMap.foreach(l => logger.info((l._1 == path) + "\t["+ l._1 + "]"))
      //处理监听该文件的事件
      if(confListenerMap.containsKey(path)){
        val result = processConfChange(conf, confMap.get(path))
        confListenerMap.get(path).confChange(path.replace(confRoot, ""), result)
      }
//      if(confListenerMap.contains(path)){
//        confListenerMap.get(path).callBack(path, result)
//      }


    }

    override def confDelete(path: String): Unit = {
      logger.info("delete path {}", path)
    }

    override def equals(obj: scala.Any): Boolean = obj.hashCode() == key


    override def hashCode(): Int = key
  }

  /**
   * 处理文件的依赖配置并监听文件变化
   * @param path
   */
  def processFile(path: String):Unit = {

    logger.info("处理配置文件{}", path)
    val conf = zKClientWrapper.getValue(path)
    logger.info("处理配置文件{} size {}", path, if(conf == null) 0 else conf.length)

    val dependencyList = ConfUtils.analyze(conf)//.map(l => getPath(l))

    confMap.put(path, dependencyList)

    dependencyList.foreach(c => {
      val p = getPath(c)
      if(confDependencyMap.containsKey(p)){
        confDependencyMap.get(p).add(path)
      }else{
        val list = new util.HashSet[String]()
        list.add(path)
        confDependencyMap.put(p, list)
      }
      logger.info("add listener {}", p)
      zKClientWrapper.addDataChangesListener(p, confCallBack)
    })

    logger.info("add listener {}", path)

    zKClientWrapper.addDataChangesListener(path, confCallBack)

  }



  /**
   * 给path添加监听
   * @param path
   * @param confCallBack
   * @return
   */
  def addPathListener(path: String, confCallBack: ConfCallBack) = {

    val p = getPath(path)
    processFile(p)
    confListenerMap.put(p, confCallBack)
  }

  /**
   * 给path添加监听并马上初始化
   * @param path
   * @param confCallBack
   */
  def addPathListenerAndInit(path: String, confCallBack: ConfCallBack) = {

      addPathListener(path, confCallBack)

      val p = getPath(path)
      confCallBack.confChange(p.replace(confRoot, ""), processConfChange(getConf(path), confMap.get(p)))
  }

  def ifExist(path: String):Boolean = {

    zKClientWrapper.ifExist(getPath(path))
  }
  def addDirListener(path: String, confCallBack: ConfCallBack, hostname: String) = {

    //注册slave里面都有什么app
    try{
      logger.info("注册app {}, {}", getPath("_status/proxy/" + hostname + "/" + path), getPath("_status/application/" + path + "/" + hostname))


      zKClientWrapper.addEphemeralPath(getPath("_status/proxy/" + hostname + "/" + path),  new Date().toLocaleString)
      zKClientWrapper.addEphemeralPath(getPath("_status/application/" + path + "/" + hostname),  new Date().toLocaleString)
    }catch {
      case e: Exception => e.printStackTrace()
      case _ =>
    }

    val absolutePath = getPath(path)
    zKClientWrapper.listConf(absolutePath).foreach( p => {

      try{
        addPathListenerAndInit(path + "/" + p, confCallBack)
      }catch {
        case e:Exception => logger.error("", e)
        case _ =>
      }

    })


    pathChildDetail.put(absolutePath, new util.HashSet(zKClientWrapper.listConf(absolutePath).map(absolutePath + "/" + _)))
    zKClientWrapper.client.subscribeChildChanges(absolutePath, new IZkChildListener {

      override def handleChildChange(parentPath: String, currentChilds: util.List[String]): Unit = {

        val childs = currentChilds.map(parentPath + "/" + _)

        val paths = processDirChildrenChange(parentPath, childs)

        paths._1.foreach(p => {
          if(!confMap.containsKey(p)){
            logger.info("创建节点{}", p)
            addPathListenerAndInit(p.replace(confRoot, ""), confCallBack)
          }
        })

        paths._2.foreach(p => {

          logger.info("删除节点{}", p)
          confListenerMap.get(p) match {
            case x:ConfCallBack => x.confDelete(p)
            case _ =>
          }
            confMap.remove(p)
            confListenerMap.remove(p)

        })

      }
    })
  }

  def processDirChildrenChange(path: String, childs:util.List[String]):Tuple2[util.Set[String], util.Set[String]] = {


    val newChilds = childs.toSet

    if(pathChildDetail.containsKey(path)){

      val oldChilds = pathChildDetail.get(path)


      val newPath = newChilds.filter(!oldChilds.contains(_))
      val delPath = oldChilds.filter(!newChilds.contains(_))

      return Tuple2(newPath, delPath)
    }

    pathChildDetail.put(path, newChilds)
    Tuple2(new util.HashSet[String](), new util.HashSet[String]())

  }

//  def start(): Unit ={
//
//    confNames.flatMap(l=> {
//      zKClientWrapper.listConf(confRoot + "/" + l).map(confRoot + "/" + l + "/" + _).toList
//    }).foreach(l => {
//
//      processConfChange(l, null)
//
//    })
//
//
//
//    confMap.foreach(l => {
//
//      zKClientWrapper.addDataChangesListener(l._1, confCallBack)
//    })
//  }


  def updateConf(path: String, conf: String) = {

    zKClientWrapper.updatePath(getPath(path), conf)
  }
  def addConf(path: String, conf: String) = {

    zKClientWrapper.addPath(getPath(path), conf)
  }

  def getConf(path: String): String = {

    zKClientWrapper.getValue(getPath(path))
  }
  def getConfByAbsolutePath(path: String): String = {

    zKClientWrapper.getValue(path)
  }

  def list(path: String):util.List[Tuple4[String, String, Boolean, Date]] = {



    val p = getPath(path)
    if(!zKClientWrapper.ifExist(p)){
      zKClientWrapper.addPath(p, null)
    }
    zKClientWrapper.listConf(p).map(l => {

      val localPath = p + "/" + l
      val time = zKClientWrapper.client.getCreationTime(localPath)
      var date: Date = null
      if(time > 0){
        date = new Date()
        date.setTime(time)
      }

      (l, path + "/" + l, l.contains("."),  date)
    }).sortWith(_._1 > _._1).toList
  }

  def delete(path: String) = {

    zKClientWrapper.client.deleteRecursive(getPath(path))
  }
  def getPath(path: String): String = {

    if(path != null && path.startsWith(confRoot)) return path
    val result = confRoot + "/" + path
    return  result.replaceAll("//+", "/").replaceAll("/$", "")

  }
  def getFinalConf(path: String):String = {

    val v = getConf(path)
    processConfChange(v, ConfUtils.analyze(v))

  }


  def processConfChange(conf: String, dependencyList: util.Set[String]): String = {


    if(dependencyList == null || dependencyList.size() == 0){
      return conf
    }else{
      val context = dependencyList.map(l => {
        (l, zKClientWrapper.getValue(getPath(l)))
      }).toMap[String, String]

      return ConfUtils.merge(conf, context)
    }


  }


  def canEqual(other: Any): Boolean = other.isInstanceOf[ConfClient]

  override def equals(other: Any): Boolean = other match {
    case that: ConfClient =>
      (that canEqual this) &&
        logger == that.logger &&
        confListenerMap == that.confListenerMap &&
        confDependencyMap == that.confDependencyMap &&
        confMap == that.confMap &&
        pathChildDetail == that.pathChildDetail &&
        confCallBack == that.confCallBack
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(logger, confListenerMap, confDependencyMap, confMap, pathChildDetail, confCallBack)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }
}
